package cn.acup.boot.config;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.PrintWriter;
import java.sql.SQLException;
import java.util.Enumeration;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import cn.acup.boot.common.ServiceException;

/**
 * web异常切面
 * @author alan.zhao
 */
@Component
@Aspect
public class WebExceptionAspect {
	
	private Logger logger = LoggerFactory.getLogger(WebExceptionAspect.class);
	
	@Autowired(required = false)
	private HttpServletRequest request;
	
	@Pointcut("execution(public * cn.acup.boot..*.controller.*.*(..))")
	private void webPointcut() {}

    private long respTimeLimit;

    ThreadLocal<Long> startTime = new ThreadLocal<>();

	public long getRespTimeLimit() {
		return respTimeLimit;
	}

	public void setRespTimeLimit(long respTimeLimit) {
		this.respTimeLimit = respTimeLimit;
	}

	/**
	* 拦截web层异常，记录异常日志，并返回友好信息到前端
	* 目前只拦截Exception，是否要拦截Error需再做考虑
	* @param e 异常对象
	*/
	@AfterThrowing(pointcut = "webPointcut()", throwing = "e")
	public void handleThrowing(JoinPoint joinPoint, Exception e) {
		
		// 其他异常信息处理
		String errorMsg = "系统处理异常！";
		
		Signature signature = joinPoint.getSignature();
        String methodName = signature.getName();
        String targetName = signature.getDeclaringTypeName();
		
		// 业务异常 取业务异常messag
		if(e instanceof ServiceException){
			errorMsg = StringUtils.isEmpty(e.getMessage()) ? "业务处理异常" : e.getMessage();
		}
		
		// 处理sql 异常
		if(e.getCause() instanceof SQLException){
			logger.error(targetName + " 执行：" + methodName +" 方法，出现SQL异常 " );
			errorMsg = "执行Sql出错";
		}
		
		// 常用异常处理
		if(e instanceof NullPointerException){
			errorMsg = "系统处理异常：空指针";
			logger.error( targetName + " 执行：" + methodName + "方法 出现空指针异常");
		}
		
		if(e instanceof ArrayIndexOutOfBoundsException){
			errorMsg = "系统处理异常：数值越界";
			logger.error( targetName + " 执行：" + methodName + "方法 出现数值越界异常");
		}
		
		if(e instanceof ClassCastException){
			errorMsg = "系统处理异常：类型强制转换异常";
			logger.error( targetName + " 执行：" + methodName + "方法 类型强制转换异常");
		}
		
		if(e instanceof FileNotFoundException){
			errorMsg = "系统处理异常：文件未找到异常";
			logger.error( targetName + " 执行：" + methodName + "方法 文件未找到异常");
		}
		
		if(e instanceof IllegalArgumentException){
			errorMsg = e.getMessage();
			logger.error( targetName + " 执行：" + methodName + "方法 IllegalArgumentException异常");
		}
		
		// 只打印非线上一次
		e.printStackTrace();
		
		writeContent(errorMsg);
	}
	
	@Before("webPointcut()")
	public void doBefore(JoinPoint joinPoint){
		startTime.set(System.currentTimeMillis());
		
		String queryString = "";
		
		if(RequestMethod.GET.name().equals(request.getMethod()) 
				|| RequestMethod.DELETE.name().equals(request.getMethod())){
			
			// get 形式参数获取
			Enumeration<String> pNames=request.getParameterNames(); 
			while(pNames.hasMoreElements()){ 
				
				String name = pNames.nextElement(); 
				String value = request.getParameter(name); 
				queryString += name + "=" + value + "&";
			} 
			
		}else{
			
			// post put解析参数
			Object[] params = joinPoint.getArgs();
			
			for(Object p : params){
				queryString += " params：" + p.toString();
			}
		}
		
		
		logger.warn("Request URL -> " + request.getScheme() + "://" + request.getServerName() + 
				":" + request.getServerPort() + request.getRequestURI() + "; Method -> " + request.getMethod() +
				"\n Request Params ->" + queryString);
	}
	
	@AfterReturning(pointcut="webPointcut()", argNames="ret", returning="ret")
	public void doAfterReturning(JoinPoint joinPoint, Object ret) throws Throwable {
    	// 处理完请求，返回内容
    	Signature signature = joinPoint.getSignature();
        String methodName = signature.getName();
        String targetName = signature.getDeclaringTypeName();
        long costTime = System.currentTimeMillis()-startTime.get();

        if(costTime >= this.respTimeLimit){
        	logger.warn(targetName+"->"+methodName+"@["+costTime+"]毫秒");
        }else{
        	logger.warn(targetName+"->"+methodName+"@["+costTime+"]毫秒");
        }
    }
	
	/**
	* 将内容输出到浏览器
	* @param content 输出内容
	*/
	private void writeContent(String content) {
		HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();
		response.reset();
		response.setCharacterEncoding("UTF-8");
		response.setHeader("Content-Type", "text/plain;charset=UTF-8");
		response.setHeader("icop-content-type", "exception");
		PrintWriter writer = null;
		try {
			writer = response.getWriter();
		} catch (IOException e) {
			e.printStackTrace();
		}
		finally {
			writer.print("failed");
			writer.flush();
			writer.close();
		}
	}
}